package com.ccrfid.rmc6.appconfig.auth;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.ErrorMessage;
import com.ccrfid.rmc6.pojo.Constants;
import com.ccrfid.rmc6.resource.auth.pojo.UserPrincipal;
import io.jsonwebtoken.*;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import java.util.stream.Collectors;

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        try {
            //验证 Authorization header
            validateTokenBasedAuthentication(authorizationHeader);
            //验证token有效性
            String token = authorizationHeader.substring(Constants.authType.length()).trim();
            validateToken(token);
            //注入用户信息
            final SecurityContext securityContext = requestContext.getSecurityContext();
            requestContext.setSecurityContext(new SecurityContext() {
                @Override
                public Principal getUserPrincipal() {
                    Claims claims = Jwts.parser().setSigningKey(Constants.signKey).parseClaimsJws(token).getBody();
                    Integer userId = (Integer) claims.get("userId");
                    String userAccount = (String) claims.get("account");
                    String userName = (String) claims.get("userName");
                    Integer cameraPermissionLevel = (Integer) claims.get("cameraPermissionLevel");
                    List<Integer> intRoles = (List<Integer>) claims.get("roles");
                    List<Long> roles = intRoles.stream().mapToLong(Integer::longValue).boxed().collect(Collectors.toList());
                    Integer personnelId = (Integer) claims.get("personnelId");
                    Integer orgId = (Integer) claims.get("orgId");
                    return new UserPrincipal(Integer.toUnsignedLong(userId), userAccount, userName,
                            cameraPermissionLevel, roles, personnelId == null ? null : Integer.toUnsignedLong(personnelId),
                            orgId == null ? null : Integer.toUnsignedLong(orgId));
                }

                @Override
                public boolean isUserInRole(String role) {
                    return true;
                }

                @Override
                public boolean isSecure() {
                    return securityContext.isSecure();
                }

                @Override
                public String getAuthenticationScheme() {
                    return Constants.authType;
                }
            });

        } catch (AppException ex) {
            abortWithUnauthorized(requestContext, ex);
        }
    }

    private void validateTokenBasedAuthentication(String authorizationHeader) throws AppException {
        boolean ok = authorizationHeader != null
                && authorizationHeader.toLowerCase().startsWith(Constants.authType.toLowerCase() + " ");
        if (!ok) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, "Authorization Header format error!", "");
        }
    }

    private void validateToken(String token) throws AppException {
        try {
            Jwts.parser()
                    .requireIssuer(Constants.issuer)
                    .setSigningKey(Constants.signKey)
                    .parseClaimsJws(token);
        } catch (ExpiredJwtException ex) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, ex.getMessage(), ex.getClass().getTypeName());
        } catch (UnsupportedJwtException ex) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, ex.getMessage(), ex.getClass().getTypeName());
        } catch (MalformedJwtException ex) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, ex.getMessage(), ex.getClass().getTypeName());
        } catch (SignatureException ex) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, ex.getMessage(), ex.getClass().getTypeName());
        } catch (IllegalArgumentException ex) {
            throw new AppException(Response.Status.UNAUTHORIZED.getStatusCode(), 999, ex.getMessage(), ex.getClass().getTypeName());
        }
    }

    private void abortWithUnauthorized(ContainerRequestContext requestContext, AppException ex) {
        requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                        .header(HttpHeaders.WWW_AUTHENTICATE, Constants.authType + " realm=\"JWT-Token\"")
                        .entity(new ErrorMessage(ex))
                        .type(MediaType.APPLICATION_JSON)
                        .build());
    }
}
